
# required module init file
#==============================================================================
#
#  $Id$
#
"""
    NOTE: The following is documentation of the OLD STYLE INTERFACE, which is 
    deprecated.  You probably want to view the Tutorial or odb.iface2 instead.

    Wrapper classes for databases that hide the underlying implementation
    details.
    
    For more information on the details of the specific object types, see 
    the documentation in the odb.odbi module.
    
    The following description covers usage for the abstract interface.  You 
    can also use the FULL back-end directly, see odb.full for more 
    information.
 
    Usage:
 
        from odb import Env
  
        # open or create a database environment.  add "prefer = FULL" or 
        # "prefer = BDB" to select a preferred back-end if we need to create 
        # one.  Default is FULL.
        env = Env('mydb')
  
        # get the B-Tree database "data"
        data = env.getBTreeDB('data')
  
        # store objects in it (keys must be strings, values can be any 
        # pickleable type.
        data['first'] = 100
        data['second'] = "this is a string"
        data['third'] = MyClass(1, 2, 3)
        
        # transactional updates
        txn = env.getTxn()
        data.set(txn, 'atomic', 'more')
        data.set(txn, 'commit', 'stuff')
        data.delete(txn, 'removable')
        txn.commit() # or "txn.abort()" to roll both changes back
        
        # cursors - warning: cursors create transactions that live for the 
        # life of the cursor.
        for key, value in data.cursor():
            print key, value

        # more ways to use a cursor (first = (key, value))
        cur = data.cursor()
        first = cur.next()
        cur.close()           # explicitly closing the cursor frees the 
                              # transaction
    
    There are three database back-ends supported: 
    
        BDB: Berkeley DB
        MEM: A simple in-memory database that stores transactions in a logfile 
            and does checkpointing by simply dumping all of the databases to 
            an enormous state file.
        FULL: (The default) This extends MEM to implement a lazy-loading 
            backing store.
    
    These can be specified as the "preferred" back-end when creating your 
    environment object:
        
        from odb import Env, BDB
        
        # use the BDB back-end
        env = Env('mydb', BDB)
        
    Note that if "mydb" already exists, the system will automatically attempt 
    to determine what type of database it is and use the appropriate back-end 
    module.  The preferred type is only used when the database does not exist 
    and we are creating a new one.

    For databases that use the FULL and MEM back-ends, transactions lock the 
    entire datatabase.  If you attempt to open a second transactions from a 
    thread that already has an open transaction, you will get a 
    "DoubleLockException."   Since it is often hard to figure out why you have 
    two transactions open, the DoubleLockException includes the traceback 
    from where the original transaction was created as part of its text.
    
    Pretty much everything that gets or changes database contents operates in 
    the context of a transaction.  If a method doesn't accept a transaction, 
    or the transaction is an optional argument and you don't pass one in, a 
    transaction will be created for you implictly.  This is mostly not a 
    problem: methods that create transactions usually clean them up before 
    they return.  However, there are at least two cases where this can become 
    an issue:
        
    -   Cursors will create an implicit transaction that remains in existence 
        for the life of the cursor.  Oftentimes, the rules of garbage 
        collection make it hard to tell when a cursor will be destroyed, so it 
        is usually best to explicitly close the cursor by calling its 
        "close()" method.
    -   Since the bracket-style (`__getitem__/__setitem__`) accessors cannot 
        be passed a transaction, they always try to create one.  Therefore, 
        you should avoid using these operators in the scope of a transaction.
    
    API Patterns
    ------------
    
    The ODB database ("getXXXDB") classes support two flavors of APIs:

    -   the pythonic access methods (get(), insert(), [] operator...)
    -   the special native methods (set(), delete())
    
    In the pythonic methods, the transaction is always optional and is passed 
    in as the last positional parameter (or the "txn" keyword parameter).  If 
    no transaction is passed in, a transaction will be created implicitly.  
    Obviously this rule doesn't apply to the [] operator because there's no 
    way to pass in an additional parameter, so the bracket operators cannot be 
    used in a transactional context.

    For the native access methods, the transaction is always required and it 
    is always passed in as the first argument.  I apologize for the asymmetry 
    :-).
"""
#
#   Copyright (C) 2006 Michael A. Muller
#   Portions Copyright 2008 Google Inc.
#
#   This file is part of ODB.
#
#   ODB is free software: you can redistribute it and/or modify it under the 
#   terms of the GNU Lesser General Public License as published by the Free 
#   Software Foundation, either version 3 of the License, or (at your option) 
#   any later version.
#
#   ODB is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU Lesser General Public License for more details.
#
#   You should have received a copy of the GNU Lesser General Public License 
#   along with ODB.  If not, see <http://www.gnu.org/licenses/>.
#
#==============================================================================
import odb, sys, os
from iface2 import getStore, IntegrityError, Map, NoGlobalStoreError, \
    OverwriteError, setStore, Sequence, Store, txnFunc
from mem import Filer

BDB = 0
MEM = 1
FULL = 2

def _pickDatabase(path, restore, prefer = FULL):

    # if the path exists, we're dealing with an existing database
    if os.path.exists(path):
        
        # check for full
        if os.path.exists(os.path.join(path, '.full')):
            import full
            return full.FullEnv(path)
            
        # check for mem
        if os.path.exists(os.path.join(path, '.mem')):
            import mem
            return mem.MemEnv(path)

        # check for berkeley
        for file in os.listdir(path):
            import bdb
            if bdb._logRx.match(file):
                return bdb.BDBEnv(path, restore)

        raise ValueError('Directory %s exists but does not contain a '
                          'recognizable database' % path
                         )

    # create based on preference
    elif prefer == BDB:
        import bdb
        return bdb.BDBEnv(path, restore)

    elif prefer == MEM:
        import mem
        return mem.MemEnv(path)
    
    elif prefer == FULL:
        import full
        return full.FullEnv(path)

    else:
        raise ValueError("bad value for 'prefer': %s" % prefer)

class Env:
    """
        A database environment.  Groups together a set of databases.
    """

    def __init__(self, path, recover = False, prefer = FULL):
        self.__impl = _pickDatabase(path, recover, prefer)

    def getBTreeDB(self, name, allowDups = False):
        return self.__impl.getBTreeDB(name, allowDups)

    def getHashDB(self, name, allowDups = False):
        return self.__impl.getHashDB(name, allowDups)

    def getTxn(self):
        """
	    Returns a new transaction for the environment.  This is a raw BDB
	    transaction.  See the @Txn class for documentation.
        """
        return self.__impl.getTxn()

    def startDeadlockThread(self, deadlockCheckInterval, noticeCallback):
        """
            Start the deadlock detection thread.

            [Added for berkeley db, need to do something different]
        """
        self.__impl.startDeadlockThread(deadlockCheckInterval, noticeCallback)

    def getDatabaseFiles(self):
        """
            Returns the list of the names of all of the files for the 
            database. """
        return self.__impl.getDatabaseFiles()

    def getAllLogs(self):
        """
            Returns a list of the names of all of the logfiles for the 
            database. """
        return self.__impl.getAllLogs()
    
    def checkpoint(self):
        """
            Forces checkpointing of the database.  Checkpointing is when we 
            transfer the in-memory data structures to their backing store 
            files.  If you don't checkpoint occasionally, you will have really 
            long startup times and have to keep the entire database in memory 
            all of the time.
        """
        self.__impl.checkpoint()

# vim:sw=4:
